package ga;

import static java.lang.System.arraycopy;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.util.Map.Entry;

public class SampleGeneticAlgorithm {
	
	private static final float MUTATION_CHANCE = .05f;
	private static final int ITERATIONS = 10;
	private static final int CHROMOSOME_GENES = 50;
	private static final int POPULATION = 20;

	public static void main(String[] args) {
		new SampleGeneticAlgorithm().go();
	}

	private boolean[][] population;
	private final Random random = new Random();

	private void go() {
		initialize();
		for(int i=0; i<ITERATIONS; i++) {
			initializeGeneration();
			final Iterator<boolean[]> iterator = fitter.values().iterator();
			next(iterator);
			next(iterator);
			boolean[] parent1 = null, parent2 = null;
			while(iterator.hasNext()) {
				parent2 = iterator.next();
				parent1 = next(iterator);
				crossover(parent1, parent2);
			}
			elitistm(parent1, parent2);
			fitter = generation;
			log((i+1) + "-th generation {");
			for(Entry<Key, boolean[]> chromosome: fitter.entrySet()) {
				log(chromosome);
			}
			log("}");
		}
	}

	private void initializeGeneration() {
		generation = new TreeMap<Key, boolean[]>();
	}

	private boolean[] next(Iterator<boolean[]> iterator) {
		if(!iterator.hasNext()) throw new RuntimeException("population not in pairs");
		return iterator.next();
	}

	private void elitistm(boolean[] parent1, boolean[] parent2) {
		selection(parent1, generation);
		selection(parent2, generation);
	}

	private void crossover(boolean[] parent1, boolean[] parent2) {
		final int point = nextPoint();
		final boolean[] offspring1 = new boolean[CHROMOSOME_GENES];
		final boolean[] offspring2 = new boolean[CHROMOSOME_GENES];
		arraycopy(parent1, 0, offspring1, 0, point);
		arraycopy(parent2, 0, offspring2, 0, point);
		arraycopy(parent1, point, offspring2, point, CHROMOSOME_GENES-point);
		arraycopy(parent2, point, offspring1, point, CHROMOSOME_GENES-point);
		mutation(offspring1);
		mutation(offspring2);
		selection(offspring1, generation);
		selection(offspring2, generation);
	}

	private int nextPoint() {
		return random.nextInt(CHROMOSOME_GENES);
	}

	private void mutation(boolean[] offspring) {
		if(random.nextFloat() < MUTATION_CHANCE) {
			final int point = nextPoint();
			offspring[point] = !offspring[point]; 
		}
	}

	Map<Key, boolean[]> fitter = new TreeMap<Key, boolean[]>();
	Map<Key, boolean[]> generation;

	private void selection(boolean[] chromosome) {
		final Double score = fitness(chromosome);
		final Key key = new Key(score);
		fitter.put(key, chromosome);
	}

	private void selection(boolean[] chromosome, Map<Key, boolean[]> generation) {
		final Double score = fitness(chromosome);
		final Key key = new Key(score);
		generation.put(key, chromosome);
	}
	
	private Double fitness(boolean[] chromosome) {
		double score = 0;
		for(boolean gene: chromosome) {
			score += (gene)? 1:0;
		}
		return score;
	}

	private void initialize() {
		population = new boolean[POPULATION][CHROMOSOME_GENES];
		log("population {");
		for(int i=0;i<POPULATION;i++) {
			for(int j=0;j<CHROMOSOME_GENES;j++) {
				population[i][j] = random.nextBoolean(); 
			}
			selection(population[i]);
			log(population[i]);
			
		}
		log("};");
	}

	private void log(String string) {
		System.out.println(string);
	}

	private void log(boolean[] bs) {
		System.out.println(Arrays.toString(bs).replaceAll("false", "0").replaceAll("true", "1"));
	}

	private void log(Entry<Key, boolean[]> chromosome) {
		System.out.println(Arrays.toString(chromosome.getValue()).replaceAll("false", "0").replaceAll("true", "1") + " : " + chromosome.getKey().getValue());
	}

	//el unico fin es generar claves unicas, para cromosmas cuyo fitness es identico
	
	private int generatorId = 0;
	
	private class Key implements Comparable<Key> {
		private Double value;
		private Integer id;
		public Key(Double value) {
			id = generatorId++;
			this.value = value;
		}
		public int compareTo(Key o) {
			final int compareValue = value.compareTo(o.getValue());
			if(compareValue == 0) {
				return id.compareTo(o.getId());
			}
			return compareValue;
		}
		public Integer getId() {
			return id;
		}
		public Double getValue() {
			return value;
		}
	}
	
	

}
